#include "Lock.h"

#define DEBUG    0

void convert_bit(unsigned char *dst, int offset, unsigned char b, int count) {
    for (int i = 0; i < count; i++) {
        dst[offset + i] = (b >> i) & 1;
    }
}

unsigned short check_sum(unsigned char *data, int len) {
    unsigned short sum = 0;
    for (int i = 0; i < len; i++) {
        sum += data[i];
    }
    return sum;
}

string bytes_2_hex(unsigned char data[], int length) {
    string hex;
    char ch[4] = {0};
    for (int i = 0; i < length; i++) {
        sprintf(ch, "%02X ", data[i]);
        hex += ch;
    }
    return hex;
}

bool resolve_data(unsigned char *src, int size, unsigned short &cmd, unsigned char *payload,
                  unsigned short &payload_len) {
    LOCK_PKT_HEADER2 *pkt_header = (LOCK_PKT_HEADER2 *) src;
    if (pkt_header->header != PKT_HEADER) {
        return false;
    }
    unsigned short len = htons(pkt_header->len);
    if (len > size - 4) {
        return false;
    }
    if (src[len + 3] != PKT_END) {
        return false;
    }
    unsigned short sum1 = check_sum(&src[1], len);
    unsigned short sum2 = ((src[len + 1] & 0xff) << 8) | (src[len + 2] & 0xff);
    if (sum1 != sum2) {
        return false;
    }
    cmd = htons(pkt_header->cmd);
    payload_len = len - 8;
    int offset = sizeof(LOCK_PKT_HEADER2);
    memcpy(payload, &src[offset], payload_len);
    return true;
}

Lock::Lock() {
    sem_init(&cmd_semaphore, 0, 0);
    read_thread_flag = false;
    inspect_thread_flag = false;
    memset(opened, 0, sizeof(opened));
}

Lock::~Lock() {
    sem_close(&cmd_semaphore);
    sem_destroy(&cmd_semaphore);
}

int Lock::connect(const char *ip, short port, unsigned char cabinetId) {
    int rc;
    strncpy(this->ip, ip, sizeof(this->ip));
    this->cabinetId = cabinetId;
    if (socket2.open_s(ip, port) == LOCK_OK) {
        /** 创建线程读取网络数据 */
        read_thread_flag = true;
        rc = pthread_create(&read_thread, NULL, ReadThreadProc, this);
        if (0 != rc) {
            perror("Create read thread failed\n");
            return LOCK_FAIL;
        }

        start_inspect();
        return LOCK_OK;
    }
    return LOCK_FAIL;
}

void *Lock::ReadThreadProc(void *args) {
    Lock *self = (Lock *) args;
    JNIEnv *env;
    jmethodID onClosed = NULL;
    bool cached = false;
    if (self->jvm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        int status = self->jvm->AttachCurrentThread((void **)&env, NULL);
        if (status < 0) {
            perror("ReadThread failed to attach current thread\n");
            return 0;
        }
    }
    jclass cls = env->GetObjectClass(self->thiz);
    if (cls != NULL) {
        onClosed = env->GetMethodID(
                cls,
                "onClosed",
                "(I)V");
    }

    unsigned char read_temp[MAX_READ_LEN];
    int read_count;
    unsigned short cmd = 0;
    unsigned short payload_len = 0;
    unsigned char payload[1024] = {0};
    bool res;

    while (self->read_thread_flag) {
        read_count = 0;
        read_count = self->socket2.read_s((char *) read_temp, MAX_READ_LEN);
        if (read_count > 0 && read_count < MAX_READ_LEN) {
            cmd = 0;
            payload_len = 0;
            res = false;

            res = resolve_data(read_temp, read_count, cmd, payload, payload_len);
            if (res) {
                if (cmd == METHOD_BOX_OPENED) {
                    sem_post(&self->cmd_semaphore);
                } else if (cmd == METHOD_INSPECTED) {
                    unsigned char status[60] = {1};
                    for (int i = 0; i < 7; i++) {
                        convert_bit(status, i * 8, payload[i + 12], 8);
                    }
                    convert_bit(status, 56, payload[19], 4);
                    if (!cached) {
                        memcpy(self->opened, status, 60);
                        cached = true;
                        string hex = bytes_2_hex(status, sizeof(status));
#if DEBUG
                        printf("lock status: %s\n", hex.c_str());
#endif
                    }
                    for (int i = 0; i < 60; i++) {
                        if (self->opened[i] == 1 && status[i] == 0) {
                            self->opened[i] = 0;
                            if (onClosed != NULL) {
                                env->CallVoidMethod(self->thiz, onClosed, i + 1);
                            }
                        }
                    }
                }
            }
        }
        usleep(10 * 1000);
    }
    self->jvm->DetachCurrentThread();
    return 0;
}

int Lock::disconnect() {
    if (read_thread_flag) {
        read_thread_flag = false;
        pthread_join(read_thread, NULL);
    }
    if (inspect_thread_flag) {
        inspect_thread_flag = false;
        pthread_join(inspect_thread, NULL);
    }
    socket2.close_s();
    return LOCK_OK;
}

int Lock::write_cmd(unsigned short cmd, unsigned short cabinet_id, unsigned char box_id) {
    LOCK_PKT_HEADER header;
    header.header = PKT_HEADER;
    header.len = htons(9);
    header.cabinet_id = htons(cabinet_id);
    header.box_id = box_id;
    header.cmd = htons(cmd);
    unsigned char data[13] = {0};
    int len = sizeof(header);
    memcpy(data, &header, len);
    data[len] = cmd >> 8;
    data[len + 1] = cmd;
    unsigned short sum = check_sum(&data[1], len + 1);
    data[len + 2] = sum >> 8;
    data[len + 3] = sum;
    data[len + 4] = PKT_END;
#if DEBUG
    string hex = bytes_2_hex(data, 5 + len);
    printf(">>> %s", hex.c_str());
#endif
    return socket2.write_s((const char *) data, sizeof(data));
}

int Lock::open(unsigned char port) {
    if (port < 1 || port > 60) {
        return LOCK_FAIL;
    }
    if (write_cmd(METHOD_OPEN_BOX, cabinetId, port) > 0) {
        struct timespec ts = sem_add_ms(500);
        int rc = sem_timedwait(&cmd_semaphore, &ts);
        if (rc > -1) {
            opened[port - 1] = 1;
        }
        return rc > -1 ? LOCK_OK : LOCK_FAIL;
    }
    return LOCK_FAIL;
}

void *Lock::InspectThreadProc(void *args) {
    Lock *self = (Lock *) args;
    int ms = 0;
#if DEBUG
    printf("InspectThread created\n");
#endif
    while (self->inspect_thread_flag) {
        ms = 0;
        while (ms < 500 && self->inspect_thread_flag) {
            usleep(10 * 1000);
            ms += 10;
        }
        if (self->inspect_thread_flag) {
            self->write_cmd(METHOD_INSPECT, self->cabinetId, 0);
        }
    }
#if DEBUG
    printf("InspectThread exit\n");
#endif
    return 0;
}

int Lock::start_inspect() {
    if (!inspect_thread_flag) {
        /** 创建线程检查巡检 */
        inspect_thread_flag = true;
        int rc = pthread_create(&inspect_thread, NULL, InspectThreadProc, this);
        if (0 != rc) {
            perror("Create inspect thread failed\n");
        }
        return rc;
    }
    return LOCK_OK;
}

int Lock::stop_inspect() {
    if (inspect_thread_flag) {
        inspect_thread_flag = false;
        pthread_join(inspect_thread, NULL);
    }
    return LOCK_OK;
}

int Lock::get_opened(unsigned char *opened, int &len) {
    len = sizeof(this->opened);
    memcpy(opened, this->opened, len);
    return LOCK_OK;
}

